package com.spzx.common.redis.util;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class RedisLockUtil {
    //获取锁的方法
    public static void lock(StringRedisTemplate stringRedisTemplate,
                            String lockKey , String uuid , int timeout){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,
                uuid, timeout, TimeUnit.SECONDS);
        if(!flag){
            //获取锁失败
            try {
                Thread.sleep(50 + new Random().nextInt(20, 100));
                //递归调用自己 再次尝试获取锁
                lock(stringRedisTemplate,lockKey,uuid,timeout);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        //获取锁成功 结束当前方法(结束自旋)
        //开启看门狗线程: 锁的键、锁的值、锁的过期时间
        renewLock(stringRedisTemplate,lockKey, uuid, timeout);
    }
    //释放锁的方法
    public static void unlock(StringRedisTemplate stringRedisTemplate , String lockKey,
                              String uuid){
        String delLockLua = "if KEYS[2] == redis.call('get' , KEYS[1]) " +
                " then return redis.call('del' , KEYS[1]) " +
                " else return 0 end";
        //execute执行lua脚本的方法
        Boolean delFlag = (Boolean) stringRedisTemplate.execute(new DefaultRedisScript(delLockLua, Boolean.class),
                Arrays.asList(lockKey, uuid));
        //"89b25e36a3014778b18664405f073fd1"  spzx:channel:test:lock
        System.out.println(delFlag ? "释放锁成功" : "释放锁失败");
    }


    private static void renewLock(StringRedisTemplate stringRedisTemplate , String lockKey, String uuid, int timeout) {
        // 等锁过期时间的2/3之后给锁续期
        new Thread(() -> {
            try {
                //1、让看门狗线程休眠2/3秒后 进行续期
                TimeUnit.SECONDS.sleep(timeout * 2 / 3);
                //2、判断锁是不是自己的，如果是则续期: 如果while循环不执行表示锁不是自己的看门狗线程结束
                while(uuid.equals(stringRedisTemplate.opsForValue().get(lockKey))){
                    stringRedisTemplate.expire(lockKey , timeout , TimeUnit.SECONDS);
                    //3、续期后 再次休眠2/3的过期时间 进行下一次续期
                    TimeUnit.SECONDS.sleep(timeout * 2 / 3);
                }

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();


    }
}
